
using System;
using System.Net;
using System.IO;
using System.Web;
using System.Reflection;
using System.Xml;


namespace CodePlex.Resourceful
{

    public class HttpServer : IDisposable
    {

        private HttpListener _listener;
        private bool _started;

        public event Action<HttpListenerContext> ReceiveWebRequest;

        private AuthenticationSchemeSelector _auth;



        private readonly Uri _rootUri;

        public HttpServer(Uri rootUri)
        {

         
            if (!rootUri.ToString().EndsWith("/"))
            {
                rootUri = new Uri(rootUri.ToString() + "/");
            }


            _rootUri = rootUri;
        }



        public AuthenticationSchemeSelector AuthenticationSchemeSelector
        {
            get
            {
                return _auth;
            }
            set
            {
                _auth = value;
            }
        }

        public Uri RootUri
        {
            get
            {
                return _rootUri;
            }
        }

        public void Start()
        {

            if (_started)
            {
                return;
            }



            if (_listener == null)
            {
                _listener = new HttpListener();
            }



          

            _listener.AuthenticationSchemeSelectorDelegate = delegate(HttpListenerRequest request)
            {
                if (_auth != null)
                {
                    return _auth(request);
                }
                else
                {
                    return AuthenticationSchemes.Anonymous;
                }
            };

            _listener.Prefixes.Add(this.RootUri.ToString());


            _started = true;

            _listener.Start();

            _listener.BeginGetContext(new AsyncCallback(WebRequestCallback), _listener);

        }



        public void Stop()
        {

            if (_listener != null)
            {
                _listener.Close();
                _listener.Abort();

                _listener = null;

                _started = false;

            }

        }


        protected void WebRequestCallback(IAsyncResult result)
        {

            if (_listener == null)
            {
                return;
            }

            try
            {
                HttpListenerContext context = _listener.EndGetContext(result);


                _listener.BeginGetContext(new AsyncCallback(WebRequestCallback), _listener);

                this.ProcessWebRequest(context);

                this.OnReceiveWebRequest(context);

            }
            catch (HttpListenerException) 
            {
               
            }

          

        }

        virtual protected void ProcessWebRequest(HttpListenerContext context) {

        }



        protected void OnReceiveWebRequest(HttpListenerContext context)
        {
            if (this.ReceiveWebRequest != null)
            {
                this.ReceiveWebRequest(context);
            }
        }







        public static void ServeForward(Uri forwardTo, HttpListenerContext context)
        {

            Uri requestUri = new Uri(forwardTo, context.Request.Url.PathAndQuery);
            HttpWebRequest request = HttpWebRequest.Create(requestUri) as HttpWebRequest;



            request.Method = context.Request.HttpMethod;
            foreach (string requestHeader in context.Request.Headers)
            {

                if (requestHeader == HttpHeader.ContentLength)
                {
                    request.ContentLength = long.Parse(context.Request.Headers[requestHeader]);
                }
                else if (requestHeader == HttpHeader.ContentType)
                {
                    request.ContentType = context.Request.Headers[requestHeader];
                }
                else if (requestHeader == HttpHeader.Expect)
                {
                    //   request.Expect = context.Request.Headers[requestHeader];
                }
                else if (requestHeader == HttpHeader.Host)
                {
                    //   request.Expect = context.Request.Headers[requestHeader];
                }
                else if (requestHeader == HttpHeader.Connection)
                {
                    //request.Connection = context.Request.Headers[requestHeader];
                }
                else if (requestHeader == HttpHeader.Accept)
                {
                    request.Accept = context.Request.Headers[requestHeader];
                }
                else if (requestHeader == HttpHeader.IfModifiedSince)
                {
                    request.IfModifiedSince = DateTime.Parse(context.Request.Headers[requestHeader]);
                }
                else if (requestHeader == HttpHeader.Referer)
                {
                    request.Referer = context.Request.Headers[requestHeader];
                }
                else
                {
                    request.Headers[requestHeader] = context.Request.Headers[requestHeader];
                }
            }


            if (request.Method == HttpMethod.Put)
            {
                using (Stream input = context.Request.InputStream)
                {
                    using (Stream output = request.GetRequestStream())
                    {
                        Utils.BufferedTransfer(input, output);
                    }
                }
            }




            WebResponse response = null;

            try
            {
                response = request.GetResponse();
            }
            catch (WebException ex)
            {
                response = ex.Response;
            }

            context.Response.ContentType = response.ContentType;
            if (response.ContentLength >= 0)
            {
                context.Response.ContentLength64 = response.ContentLength;
            }

            using (Stream input = response.GetResponseStream())
            {
                using (Stream output = context.Response.OutputStream)
                {
                    Utils.BufferedTransfer(input, output);
                }
            }


            context.Response.Close();
        }






        public static void ServeFiles(Uri rootUri, string rootDirectory, HttpListenerContext context)
        {
            if (!Directory.Exists(rootDirectory))
            {
                throw new ArgumentException("Root Directory does not exist.", "rootDirectory");
            }


          
            string path = rootUri.MakeRelativeUri(context.Request.Url).ToString();


            string filepath = Path.Combine(rootDirectory, path);


            string response = null;
            DateTime modified = DateTime.Now;

            if (Directory.Exists(filepath))
            {
                // serve folder
                

                string title = rootUri.Host + " - /" +rootUri.GetComponents(UriComponents.Path,UriFormat.UriEscaped) +  path;
                modified = new DirectoryInfo(filepath).LastWriteTime;

                StringWriter rt = new StringWriter();
                using (XmlWriter writer = XmlWriter.Create(rt))
                {
                    using (Utils.StartElement(writer, "html"))
                    {
                        using (Utils.StartElement(writer,"head"))
                        {
                            writer.WriteElementString("title", title);
                        }

                        using (Utils.StartElement(writer,"body"))
                        {

                            writer.WriteElementString("h1", title);
                            writer.WriteElementString("hr", "");

                            using (Utils.StartElement(writer, "pre"))
                            {

                                using (Utils.StartElement(writer, "a"))
                                {
                                    writer.WriteAttributeString("href", "..");

                                    writer.WriteString("[To Parent Directory]");
                                }

                                writer.WriteElementString("br", "");
                                writer.WriteElementString("br", "");

                         
                                foreach (string dir in Directory.GetDirectories(filepath))
                                {

                                    DirectoryInfo di = new DirectoryInfo(dir);
                                    string pre = string.Format("{0,39}{1,13} ", di.LastWriteTime.ToString(), "<dir>");
                                    writer.WriteString(pre);

                                    using (Utils.StartElement(writer, "a"))
                                    {
                                        writer.WriteAttributeString("href", Path.GetFileName(dir) + "/");
                                        writer.WriteString(Path.GetFileName(dir));
                                    }

                                    writer.WriteElementString("br", "");

                                }

                                foreach (string file in Directory.GetFiles(filepath))
                                {

                                    FileInfo fi = new FileInfo(file);
                                    string pre = string.Format("{0,39}{1,13} ", fi.LastWriteTime.ToString(), fi.Length);
                                    writer.WriteString(pre);

                                    using (Utils.StartElement(writer, "a"))
                                    {
                                        writer.WriteAttributeString("href", Path.GetFileName(file));
                                        writer.WriteString(Path.GetFileName(file));
                                    }

                                    writer.WriteElementString("br", "");
                                }
                            }


                            writer.WriteElementString("hr", "");
                           
                        }
                    }

                }

                context.Response.ContentType = MediaType.TextHtml;
                response = rt.ToString();


            }
            else if (File.Exists(filepath))
            {
                // serve file

                string mimeType = MimeMap.Get(filepath);

                
                context.Response.ContentType = mimeType;
                context.Response.ContentLength64 = new FileInfo(filepath).Length;
                context.Response.Headers[HttpHeader.LastModified] = Utils.ToHttpDate(new FileInfo(filepath).LastWriteTime);

                Stream output = context.Response.OutputStream;
                Stream input = File.OpenRead(filepath);
                //using (Stream output = context.Response.OutputStream)
                //{
                //    using (Stream input = File.OpenRead(filepath))
                //    {
                        long total = 0;
                        Utils.BufferedTransfer(input, output, delegate(long x)
                        {
                            total += x;
                            Console.WriteLine("{0} {1} {2}", filepath,x, total);
                        });
                //    }
                //}

                
                context.Response.Close();
                return;

            }
            else
            {
                // serve 404
                context.Response.StatusCode = HttpStatus.NotFound.Code;
                response = "<h1>404 Not Found</h1>";
            }



            Utils.PostProcess(response, new HttpListenerContextAdapter(context, rootUri), modified);







        }



       
        public void Dispose()
        {
            this.Stop();
        }

       
    }

}


